package com.naiterui.ehp.bs.wechat.utils;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import javax.validation.constraints.NotNull;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;

import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.constants.CommonConstant;
import com.naiterui.ehp.bp.support.utils.JsonMapper;
import com.naiterui.ehp.bp.utils.biz.ScheduleUtils;
import com.naiterui.ehp.bp.utils.date.DateUtil;
import com.naiterui.ehp.bp.utils.event.EventUtil;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bp.utils.net.RestHttpUtil;
import com.naiterui.ehp.bs.patient.handler.data.BizConfigEvent;
import com.naiterui.ehp.bs.patient.utils.constants.PatientConstant;
import com.naiterui.ehp.bs.wechat.constants.WeChatCache;
import com.naiterui.ehp.bs.wechat.protocol.msg.IPoster;
import com.naiterui.ehp.bs.wechat.protocol.msg.custom.CustomMsg;
import com.naiterui.ehp.bs.wechat.protocol.msg.subscribe.SubscribeAdd;
import com.naiterui.ehp.bs.wechat.protocol.msg.subscribe.SubscribeAddResult;
import com.naiterui.ehp.bs.wechat.protocol.msg.subscribe.SubscribeList;
import com.naiterui.ehp.bs.wechat.protocol.msg.subscribe.SubscribeMsg;
import com.naiterui.ehp.bs.wechat.protocol.msg.template.IndustryConfig;
import com.naiterui.ehp.bs.wechat.protocol.msg.template.TemplateAdd;
import com.naiterui.ehp.bs.wechat.protocol.msg.template.TemplateAddResult;
import com.naiterui.ehp.bs.wechat.protocol.msg.template.TemplateDelete;
import com.naiterui.ehp.bs.wechat.protocol.msg.template.TemplateList;
import com.naiterui.ehp.bs.wechat.protocol.msg.template.TemplateMsg;
import com.naiterui.ehp.bs.wechat.protocol.params.CodeActionInfo;
import com.naiterui.ehp.bs.wechat.protocol.result.AccessToken;
import com.naiterui.ehp.bs.wechat.protocol.result.AuthAccessToken;
import com.naiterui.ehp.bs.wechat.protocol.result.JsTicket;
import com.naiterui.ehp.bs.wechat.protocol.result.MaterialUpResult;
import com.naiterui.ehp.bs.wechat.protocol.result.MediaVideo;
import com.naiterui.ehp.bs.wechat.protocol.result.QrCodeResult;
import com.naiterui.ehp.bs.wechat.protocol.result.TempMediaUpResult;
import com.naiterui.ehp.bs.wechat.protocol.result.UserInfo;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;

/**
 * 微信接口封装工具
 *
 * @author zhoupeipei
 * @date 2019-06-12 13:54
 * @since 1.0.0
 */
public class WeChatApiUtil {
    /**
     * 微信参数拼接分隔符
     */
    public static final String WX_DEFAULT_PARAM_SPLIT = "0AZ0";

    private static final Logger LOGGER = LoggerFactory.getLogger(WeChatApiUtil.class);
    /**
     * client_credential
     */
    private static final String GRANT_TYPE_CLIENT_CREDENTIAL = "client_credential";
    /**
     * authorization_code
     */
    private static final String GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code";

    /**
     * web页面授权方式--无需用户授权【静默】
     */
    private static final String WECHAT_WEB_AUTHORIZE_BASE = "snsapi_base";
    /**
     * web页面授权方式--用户授权
     */
    private static final String WECHAT_WEB_AUTHORIZE_USERINFO = "snsapi_userinfo";

    /**
     * 微信获取accessToken接口地址
     */
    private static final String WECHAT_URL_FOR_ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token?";
    /**
     * 获取公众号粉丝信息
     */
    private static final String WECHT_URL_FOR_PA_USER = "https://api.weixin.qq.com/cgi-bin/user/info";
    /**
     * 获取公众号带参数二维码接口地址
     */
    private static final String WECHAT_PA_QRCODE_URL = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=";
    /**
     * 公众号模板消息发送地址
     */
    private static final String WECHAT_PA_MSG_TEMPLATE_URL = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=";
    /**
     * 公众号菜单-获取
     */
    private static final String WECHAT_PA_MENU_GET_URL = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=";
    /**
     * 公众号菜单-创建
     */
    private static final String WECHAT_PA_MENU_CREATE_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=";
    /**
     * 公众号客服消息发送通知
     */
    private static final String WECHAT_PA_MSG_CUSTOM_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=";
    /**
     * 用tiket展示二维码url地址
     */
    public static final String WECHAT_SHOW_QRCODE_URL = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=";

    /**
     * 临时素材下载地址，get(https,注意：视频下载需http)
     */
    private static final String WECHAT_TEMP_MEDIA_DOWNLOAD_URL = "https://api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s";
    /**
     * 临时素材上传地址，post/form（https）
     */
    private static final String WECHAT_TEMP_MEDIA_UPLOAD_URL = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s";
    /**
     * 永久素材上传地址，post/form（https）
     */
    private static final String WECHAT_MEDIA_UPLOAD_URL = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=%s&type=%s";
    /**
     * web页面授权
     */
    private static final String WECHAT_WEB_AUTHORIZE = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect";

    /**
     * web页面授权-获取用户accesstoken
     */
    private static final String AUTHORIZE_CODE_ACCESSTOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token?";
    /**
     * web页面授权-检测用户accesstoken是否有效
     */
    private static final String AUTHORIZE_ACCESSTOKEN_AUTH = "https://api.weixin.qq.com/sns/auth?";
    /**
     * web页面授权-刷新用户accesstoken
     */
    public static final String AUTHORIZE_RFRESH_ACCESSTOKEN = "https://api.weixin.qq.com/sns/oauth2/refresh_token?";
    /**
     * web页面授权-获取用户授权信息
     */
    public static final String AUTHORIZE_USERINFO = "https://api.weixin.qq.com/sns/userinfo?";

    /**
     * 获取jsticket
     */
    public static final String JSAPI_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi";

    /**
     * 订阅通知url
     */
    public static final String WECHAT_SUBSCRIBE_MESSAGE_BASE_URL = "https://api.weixin.qq.com/wxaapi/newtmpl/";
    /**
     * 选用模板
     */
    public static final String WECHAT_SUBSCRIBE_MESSAGE_ADD = WECHAT_SUBSCRIBE_MESSAGE_BASE_URL + "addtemplate?access_token=";
    /**
     * 删除模板
     */
    public static final String WECHAT_SUBSCRIBE_MESSAGE_DEL = WECHAT_SUBSCRIBE_MESSAGE_BASE_URL + "deltemplate?access_token=";
    /**
     * 获取公众号类目
     */
    public static final String WECHAT_SUBSCRIBE_MESSAGE_GETCATEGORY = WECHAT_SUBSCRIBE_MESSAGE_BASE_URL + "getcategory?access_token=";
    /**
     * 获取模板中的关键词
     */
    public static final String WECHAT_SUBSCRIBE_MESSAGE_GETPUBTEMPLATEKEYWORDS = WECHAT_SUBSCRIBE_MESSAGE_BASE_URL + "getpubtemplatekeywords?access_token=";
    /**
     * 获取类目下的公共模板
     */
    public static final String WECHAT_SUBSCRIBE_MESSAGE_GETPUBTEMPLATETITLES = WECHAT_SUBSCRIBE_MESSAGE_BASE_URL + "getpubtemplatetitles?access_token=";
    /**
     * 获取私有模板列表
     */
    public static final String WECHAT_SUBSCRIBE_MESSAGE_GETTEMPLATE = WECHAT_SUBSCRIBE_MESSAGE_BASE_URL + "gettemplate?access_token=";
    /**
     * 发送订阅通知
     */
    public static final String WECHAT_SUBSCRIBE_MESSAGE_SEND = "https://api.weixin.qq.com/cgi-bin/message/subscribe/bizsend?access_token=";
    public static final String WECHAT_TEMPLATE_BASE_URL = "https://api.weixin.qq.com/cgi-bin/template/";
    public static final String WECHAT_TEMPLATE_INDUSTRY_SET = WECHAT_TEMPLATE_BASE_URL + "api_set_industry?access_token=";
    public static final String WECHAT_TEMPLATE_INDUSTRY_GET = WECHAT_TEMPLATE_BASE_URL + "get_industry?access_token=";
    public static final String WECHAT_TEMPLATE_TEMPLATE_ADD = WECHAT_TEMPLATE_BASE_URL + "api_add_template?access_token=";
    public static final String WECHAT_TEMPLATE_TEMPLATE_DELETE = WECHAT_TEMPLATE_BASE_URL + "del_private_template?access_token=";
    public static final String WECHAT_TEMPLATE_TEMPLATE_GET = WECHAT_TEMPLATE_BASE_URL + "get_all_private_template?access_token=";

    /**
     * accesstoken定时刷新回调地址
     */
    private static final String ACCESS_TOKEN_REFRESH_URL = ConfigUtil.getString("wechat.access.token.refresh");

    /**
     * accesstoken 自动刷新周期  110分钟，提前4分钟刷新
     */
    private static final int ACCESS_TOKEN_REFRESH_UNIT = 111;
    private static final int WECHAT_API_RETRY_MAX = 2;

    /**
     * 客服消息失活条数（微信限制为20条，防止有三方服务发送客服消息暂定18条）
     */
    private static final int CUSTOMER_MSG_DEACTIVATION_NUM = 18;

    /**
     * @Description: 从微信获取AccessToken，并放入缓存，更新刷新任务，正常情况下，1小时50分钟执行一次，为避免并发调度导致AccessToken混乱，加锁
     * @Author: zhoupeipei
     * @Date： 2019/1/30
     */
    public static String refreshAccessToken(String appId, String secret) {
        String accessToken = "";
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("appid", appId);
        paramMap.put("secret", secret);
        paramMap.put("grant_type", GRANT_TYPE_CLIENT_CREDENTIAL);
        ResponseEntity<AccessToken> responseEntity =
                RestHttpUtil.get().url(WECHAT_URL_FOR_ACCESS_TOKEN).paramMap(paramMap).execute(new ParameterizedTypeReference<AccessToken>() {
                });
        AccessToken accessTokenResult = responseEntity.getBody();
        accessToken = accessTokenResult.getAccessToken();
        if (StringUtils.isBlank(accessToken)) {
            LOGGER.error("获取AccessToken失败，结果：{}", accessTokenResult);
            return accessToken;
        }
        //新token放入缓存
        //微信AccessToken有效期2小时,缓存1小时55分，提前失效，微信会保证新旧token5分钟内可共同使用
        RedisUtil.valueOps().setString(WeChatCache.accessTokenKey(appId), accessToken, WeChatCache.ACCESS_TOKEN_CACHE_TIME);

        // 添加定时任务，1小时51分后刷新缓存，先调任务删除接口，再添加新的调度
        Date refreshTime = DateUtil.addMinutes(new Date(), ACCESS_TOKEN_REFRESH_UNIT);
        //DateUtil.plus(new Date(), ACCESS_TOKEN_REFRESH_UNIT, ChronoUnit.MINUTES);
        String refreshTimeStr = DateUtil.formatDate(refreshTime, DateUtil.CRON_TIME_FORMAT);
        Map<String, String> scheduleParam = new HashMap<>();
        //scheduleParam.put("type", weChatType);
        try {
            //先删除未执行的刷新任务
            ScheduleUtils.removeCustomerTask(ConfigUtil.getString(PatientConstant.TASK_SCHEDULE_HOST), ScheduleUtils.TASK_TYPE_ACCESS_TOKEN, appId);
            //添加新的刷新任务
            ScheduleUtils.addCustomerTask(ConfigUtil.getString(PatientConstant.TASK_SCHEDULE_HOST), refreshTimeStr,
                                          ScheduleUtils.TASK_TYPE_ACCESS_TOKEN, appId, ACCESS_TOKEN_REFRESH_URL, scheduleParam);
        } catch (Exception e) {
            LOGGER.error("accessToken刷新任务添加失败，errmsg={}", e);
        }

        return accessToken;
    }

    /**
     * jsapi_ticket
     * <p>
     * 生成签名之前必须先了解一下jsapi_ticket，jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下，jsapi_ticket的有效期为7200秒，通过access_token来获取。</p>
     * 由于获取jsapi_ticket的api调用次数非常有限，频繁刷新jsapi_ticket会导致api调用受限，影响自身业务，开发者必须在自己的服务全局缓存jsapi_ticket 。
     * </p>
     * 文档：https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
     *
     * @param accessToken
     *
     * @return
     */
    public static JsTicket getTicket(String accessToken) {
        ResponseEntity<JsTicket> responseEntity =
                RestHttpUtil.get().url(String.format(JSAPI_TICKET_URL, accessToken)).execute(new ParameterizedTypeReference<JsTicket>() {
                });
        if (responseEntity.getBody() != null) {
            return (JsTicket) responseEntity.getBody();
        }
        return new JsTicket();
    }

    public static UserInfo getPaUserInfo(String accessToken, String openId) {
        UserInfo userInfo = new UserInfo();
        if (StringUtils.isBlank(accessToken)) {
            return userInfo;
        }
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("access_token", accessToken);
        paramMap.put("openid", openId);
        paramMap.put("lang", "zh_CN");
        ResponseEntity<UserInfo> responseEntity = RestHttpUtil.get()
                                                              .url(WECHT_URL_FOR_PA_USER)
                                                              .paramMap(paramMap)
                                                              .execute(new ParameterizedTypeReference<UserInfo>() {
                                                              });
        LOGGER.info("获取粉丝信息结果:{}", responseEntity.getBody());
        return responseEntity.getBody();
    }

    public static TempMediaUpResult tempMediaUpload(String accessToken, FileSystemResource file, String type) {
        String url = String.format(WECHAT_TEMP_MEDIA_UPLOAD_URL, accessToken, type);
        Map<String, FileSystemResource> map = new HashMap<>();
        map.put("media", file);
        ResponseEntity<TempMediaUpResult> responseEntity = RestHttpUtil.post()
                                                                       .url(url)
                                                                       .paramMap(map)
                                                                       .execute(new ParameterizedTypeReference<TempMediaUpResult>() {
                                                                       });
        LOGGER.info("多媒体上传结果,{}", responseEntity.getBody());
        return responseEntity.getBody();
    }

    public static byte[] tempMediaDownload(String accessToken, String mediaId) {
        String url = String.format(WECHAT_TEMP_MEDIA_DOWNLOAD_URL, accessToken, mediaId);
        ResponseEntity<byte[]> responseEntity = RestHttpUtil.get()
                                                            .url(url)
                                                            .execute(new ParameterizedTypeReference<byte[]>() {
                                                            });
        return responseEntity.getBody();
    }

    public static MediaVideo tempVideoDownloadUrl(String accessToken, String mediaId) {
        String url = String.format(WECHAT_TEMP_MEDIA_DOWNLOAD_URL, accessToken, mediaId);
        ResponseEntity<MediaVideo> responseEntity = RestHttpUtil.get()
                                                                .url(url)
                                                                .execute(new ParameterizedTypeReference<MediaVideo>() {
                                                                });
        return responseEntity.getBody();
    }

    public static MaterialUpResult mediaUpload(String accessToken, FileSystemResource file, String type) {
        String url = String.format(WECHAT_MEDIA_UPLOAD_URL, accessToken, type);
        Map<String, FileSystemResource> map = new HashMap<>();
        map.put("media", file);
        ResponseEntity<MaterialUpResult> responseEntity = RestHttpUtil.post()
                .url(url)
                .paramMap(map)
                .execute(new ParameterizedTypeReference<MaterialUpResult>() {
                });
        LOGGER.info("多媒体上传结果,{}", responseEntity.getBody());
        return responseEntity.getBody();
    }

    /**
     * 发送客服消息
     *
     * @param accessToken
     * @param msg
     */
    public static void sendCustomMsg(String accessToken, CustomMsg msg) {
        // 对发送客服消息做计数，如果大于这一条数则删除患者客服消息key
        Long sendMsgNum = RedisUtil.keyOps().incr(CommonConstant.CONSULT_PATIENT_SEND_MSG_NUM + msg.getToUser());
        if (sendMsgNum >= CUSTOMER_MSG_DEACTIVATION_NUM) {
            RedisUtil.keyOps().delete(CommonConstant.CONSULT_PATIENT_SEND_MSG_NUM + msg.getToUser());
            RedisUtil.keyOps().delete(CommonConstant.CONSULT_PATIENT_ACTIVE + msg.getToUser());
        }

        String url = WECHAT_PA_MSG_CUSTOM_URL + accessToken;
        String result = postWeChat(url, msg);
        LOGGER.info("推送客服消息结果，{}", result);

        //特殊异常处理，新逻辑：连续推送20条客服消息,若期间客户无回复，则客服消息失活
        //{"errcode":45047,"errmsg":"out of response count limit hint: [pTQQOA0501ge25]"}
        if (StringUtils.contains(result, "\"errcode\":45047") || StringUtils.contains(result, "\"errcode\":45015")) {
            EventUtil.publishEvent(new BizConfigEvent(msg.getToUser(), result, BizConfigEvent.BIZ_CONFIG_WXMSG_LIMIT));
        }
    }

    /**
     * 获取菜单信息
     * @param accessToken
     * @return
     */
    public static String getMenu(String accessToken){
        String url = WECHAT_PA_MENU_GET_URL + accessToken;
        String result = RestHttpUtil.get()
            .url(url)
            .execute(new ParameterizedTypeReference<String>() {
            }).getBody();
        return result;
    }

    /**
     * 设置菜单信息
     * @param accessToken
     * @param menuJson
     * @return
     */
    public static String createMenu(String accessToken, String menuJson){
        String url = WECHAT_PA_MENU_CREATE_URL + accessToken;
        String result = RestHttpUtil.post()
            .url(url)
            .body(menuJson)
            .execute(new ParameterizedTypeReference<String>() {
            }).getBody();
        return result;
    }

    /**
     * 获取行业信息
     * @param accessToken
     * @return
     */
    public static String getIndustry(String accessToken){
        String url = WECHAT_TEMPLATE_INDUSTRY_GET + accessToken;
        String result = RestHttpUtil.get()
                .url(url)
                .execute(new ParameterizedTypeReference<String>() {
                }).getBody();
        return result;
    }

    /**
     * 设置行业信息
     * @param accessToken
     * @return
     */
    public static String setIndustry(String accessToken, IndustryConfig industryConfig){
        String url = WECHAT_TEMPLATE_INDUSTRY_SET + accessToken;
        String result = RestHttpUtil.post()
                .url(url)
                .body(JSONUtil.toJsonStr(industryConfig))
                .execute(new ParameterizedTypeReference<String>() {
                }).getBody();
        return result;
    }

    /**
     * 获取已配置消息模板
     * @param accessToken
     * @return
     */
    public static TemplateList getTemplate(String accessToken){
        String url = WECHAT_TEMPLATE_TEMPLATE_GET + accessToken;
        TemplateList result = RestHttpUtil.get()
                .url(url)
                .execute(new ParameterizedTypeReference<TemplateList>() {
                }).getBody();
        return result;
    }

    /**
     * 设置消息模板
     * @param accessToken
     * @return
     */
    public static TemplateAddResult setTemplate(String accessToken, TemplateAdd templateAdd){
        String url = WECHAT_TEMPLATE_TEMPLATE_ADD + accessToken;
        TemplateAddResult result = RestHttpUtil.post()
                .url(url)
                .body(JSONUtil.toJsonStr(templateAdd))
                .execute(new ParameterizedTypeReference<TemplateAddResult>() {
                }).getBody();
        return result;
    }

    /**
     * 删除消息模板
     * @param accessToken
     * @return
     */
    public static String deleteTemplate(String accessToken, TemplateDelete templateDelete){
        String url = WECHAT_TEMPLATE_TEMPLATE_DELETE + accessToken;
        String result = RestHttpUtil.post()
                .url(url)
                .body(JSONUtil.toJsonStr(templateDelete))
                .execute(new ParameterizedTypeReference<String>() {
                }).getBody();
        return result;
    }

    /**
     * 发送模板消息
     *
     * @param accessToken
     * @param msg
     */
    public static void sendTemplateMsg(String accessToken, TemplateMsg msg) {
        String url = WECHAT_PA_MSG_TEMPLATE_URL + accessToken;
        String result = postWeChat(url, msg);
        LOGGER.info("推送模板消息结果，{}", result);
    }

    /**
     * 获取已配置消息模板
     *
     * @param accessToken
     * @return
     */
    public static SubscribeList getSubscribeList(String accessToken) {
        String url = WECHAT_SUBSCRIBE_MESSAGE_GETTEMPLATE + accessToken;
        SubscribeList result = RestHttpUtil.get()
                .url(url)
                .execute(new ParameterizedTypeReference<SubscribeList>() {
                }).getBody();
        return result;
    }

    /**
     * 选用模板
     *
     * @param accessToken
     * @return
     */
    public static SubscribeAddResult setSubscribeTemplate(String accessToken, SubscribeAdd subscribeAdd) {
        String url = WECHAT_SUBSCRIBE_MESSAGE_ADD + accessToken;
        return RestHttpUtil.post()
                .url(url)
                .body(JSONUtil.toJsonStr(subscribeAdd))
                .execute(new ParameterizedTypeReference<SubscribeAddResult>() {
                }).getBody();
    }

    /**
     * 发送订阅通知
     *
     * @param accessToken
     * @param msg
     * @return 状态码 0.成功
     * 40003.touser字段openid为空或者不正确
     * 40037.订阅模板id为空不正确
     * 43101.用户拒绝接受消息，如果用户之前曾经订阅过，则表示用户取消了订阅关系
     * 47003.模板参数不准确，可能为空或者不满足规则，errmsg会提示具体是哪个字段出错
     * 41030.page路径不正确
     */
    public static Integer sendSubscribeMsg(String accessToken, SubscribeMsg msg) {
        String url = WECHAT_SUBSCRIBE_MESSAGE_SEND + accessToken;
        String result = postWeChat(url, msg);
        LOGGER.info("推送订阅通知消息结果，{}", result);
        JSONObject jsonObject = JSONUtil.parseObj(result);
        Integer errcode = jsonObject.getInt("errcode");
        if (errcode != 0) {
            LOGGER.error("推送订阅通知消息失败，{}", JSONUtil.toJsonStr(msg));
        }
        return errcode;
    }

    /**
     * @Description: 获取字符串参数类型的永久二维码
     * @Author: zhoupeipei
     * @Date： 2019/1/28
     */
    public static QrCodeResult getQrCodeStr(@NotNull String accessToken, @NotNull String actionName, @NotNull CodeActionInfo.Scene scene) {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("action_name", actionName);
        paramMap.put("action_info", new CodeActionInfo(scene));
        try {
            String body = JsonMapper.toJson(paramMap);
            return getQrCode(accessToken, body);
        } catch (Exception e) {
            LOGGER.error("Json转换异常：{}", e);
            QrCodeResult result = new QrCodeResult();
            result.setErrmsg(e.getMessage());
            return result;
        }
    }

    /**
     * 获取微信web授权地址
     *
     * @param appid       公众号appid
     * @param redirectUrl 授权页面全路径（不含参数）
     * @param silent      是否静默授权，true：静默【不需要授权】；false：需要授权
     * @param params      @{@link Nullable} 授权页面需携带的参数信息，可为空
     *
     * @return
     */
    public static String getWebAuthorize(String appid, String redirectUrl, boolean silent, String params) {
        try {
            //redirectUrl = URLEncoder.encode(redirectUrl, "UTF-8");
            return String.format(WECHAT_WEB_AUTHORIZE, appid, redirectUrl, silent ? WECHAT_WEB_AUTHORIZE_BASE : WECHAT_WEB_AUTHORIZE_USERINFO,
                                 null == params ? "" : params);
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("url转码失败，redirectUrl:{},silent:{};params:{}", redirectUrl, silent, params);
        }
        return null;
    }

    /**
     * 获取网页授权调用凭证
     * 【同时返回了用户openID，如果是静默授权，流程到此已经可以匹配用户信息】
     *
     * @param appid
     * @param secret
     * @param code
     *
     * @return
     */
    public static AuthAccessToken getWebAuthorizeAccesstoken(String appid, String secret, String code) {
        Map<String, Object> paramMap = new HashMap<>();
        AuthAccessToken tokenResult = new AuthAccessToken();
        paramMap.put("appid", appid);
        paramMap.put("secret", secret);
        paramMap.put("code", code);
        paramMap.put("grant_type", GRANT_TYPE_AUTHORIZATION_CODE);
        ResponseEntity<AuthAccessToken> responseEntity = RestHttpUtil.get()
                                                                     .url(AUTHORIZE_CODE_ACCESSTOKEN)
                                                                     .paramMap(paramMap)
                                                                     .execute(new ParameterizedTypeReference<AuthAccessToken>() {
                                                                     });
        LOGGER.info("web授权code换取token结果,{}", responseEntity.getBody());
        tokenResult = Optional.ofNullable(responseEntity.getBody()).get();
        if (null != responseEntity.getBody()) {
            tokenResult = responseEntity.getBody();
        }
        return tokenResult;
    }

    /**
     * 远程微信请求生成二维码
     *
     * @param accessToken
     * @param paramMap
     *
     * @return
     */
    private static QrCodeResult getQrCode(String accessToken, String body) {
        QrCodeResult qrCodeResult = new QrCodeResult();
        ResponseEntity<QrCodeResult> responseEntity = RestHttpUtil.post()
                                                                  .url(WECHAT_PA_QRCODE_URL + accessToken)
                                                                  .body(body)
                                                                  .execute(new ParameterizedTypeReference<QrCodeResult>() {
                                                                  });
        if (responseEntity.getBody() != null) {
            qrCodeResult = (QrCodeResult) responseEntity.getBody();
        }

        return qrCodeResult;
    }

    /**
     * post请求微信，发送客服消息、模板消息
     *
     * @param url
     * @param msger
     *
     * @return
     */
    private static String postWeChat(String url, IPoster msger) {
        String body = "";
        try {
            body = JsonMapper.toJson(msger);
        } catch (Exception e) {
            LOGGER.error("Json转换异常：{}", e);
            return "";
        }

        int count = 1;
        ResponseEntity<String> responseEntity;
        do {
            responseEntity = RestHttpUtil.post()
                                         .url(url)
                                         .body(body)
                                         .execute(new ParameterizedTypeReference<String>() {
                                         });
        } while (HttpStatus.REQUEST_TIMEOUT.equals(responseEntity.getStatusCode()) && count++ <= WECHAT_API_RETRY_MAX);

        return responseEntity.getBody();
    }

}
